package com.gitee.wsl.lang

import com.gitee.wsl.lang.PriceDecimal.Companion.CENTS
import kotlin.math.absoluteValue
import kotlin.math.roundToLong
import kotlin.math.sign

/**
 * A decimal fixed-point number with 2 fraction digits, suitable for e.g. monetary amounts.
 * Lighter and faster than BigDecimal, but also having numeric equals.
 * Operations resulting in Long overflow will throw ArithmeticException.
 */
class PriceDecimal internal constructor(private val c: Long): Comparable<PriceDecimal>, Number() {
    companion object {
        const val CENTS = 100L
        val MAX_VALUE = PriceDecimal(Long.MAX_VALUE)
        val MIN_VALUE = PriceDecimal(Long.MIN_VALUE)
        val ZERO = PriceDecimal(0)
        val ONE = PriceDecimal(CENTS)
    }

    constructor(v: Double): this((v * CENTS).roundToLong())
    constructor(v: String): this(v.toDouble())

    operator fun unaryMinus() = PriceDecimal(-c)
    operator fun unaryPlus() = this

    operator fun plus(o: PriceDecimal) = PriceDecimal(c + o.c)
    operator fun minus(o: PriceDecimal) = PriceDecimal( c - o.c)
    operator fun times(o: PriceDecimal) = PriceDecimal(roundDiv(c * o.c, CENTS))
    operator fun div(o: PriceDecimal) = PriceDecimal(roundDiv(c * CENTS, o.c))
    operator fun rem(o: PriceDecimal) = PriceDecimal(c % o.c)

    operator fun times(o: Int) = PriceDecimal(c * o )
    operator fun times(o: Long) = PriceDecimal(c * o )
    operator fun times(o: Double) = PriceDecimal(toDouble() * o)
    operator fun div(o: Int) = div(o.toLong())
    operator fun div(o: Long) = PriceDecimal(roundDiv(c, o))
    operator fun div(o: Double) = PriceDecimal(toDouble() / o)

    private fun roundDiv(n: Long, d: Long) = (if ((n < 0) xor (d < 0)) n - d/2 else (n + d/2)) / d

    operator fun inc() = plus(1.priceDecimal)
    operator fun dec() = minus(1.priceDecimal)

    @Deprecated("Use absoluteValue instead", ReplaceWith("absoluteValue"))
    fun abs() = absoluteValue
    val absoluteValue get() = if (c < 0) PriceDecimal(-c) else this
    val sign get() = c.sign

    infix fun percent(p: PriceDecimal) = times(p) / CENTS

    override fun equals(other: Any?) = c == (other as? PriceDecimal)?.c
    override fun hashCode() = c.hashCode()
    override fun compareTo(other: PriceDecimal) = c.compareTo(other.c)

    override fun toString(): String {
        val sb = StringBuilder((c / CENTS).toString())
        val cents = (c % CENTS).absoluteValue
        if (cents > 0) sb.append('.').append(cents.toString().padStart(2, '0'))
        if (c > -CENTS && c < 0) sb.insert(0, '-')
        return sb.toString()
    }

    override fun toDouble() = c / CENTS.toDouble()
    override fun toFloat() = toDouble().toFloat()
    override fun toLong() = roundDiv(c, CENTS)
    override fun toInt() = toLong().toInt()
    override fun toShort() = toLong().toShort()
    override fun toByte() = toLong().toByte()
}

val Int.priceDecimal get() = PriceDecimal( this * CENTS)
val Long.priceDecimal get() = PriceDecimal(this * CENTS)
val Double.priceDecimal get() = PriceDecimal(this)
val Float.priceDecimal get() = PriceDecimal(toDouble())
val String.priceDecimal get() = PriceDecimal(this)

fun Iterable<PriceDecimal>.sum() = sumOf { it }
fun Collection<PriceDecimal>.average() = sum() / size.priceDecimal

inline fun <T> Sequence<T>.sumOf(selector: (T) -> PriceDecimal): PriceDecimal = asIterable().sumOf(selector)
inline fun <T> Iterable<T>.sumOf(selector: (T) -> PriceDecimal): PriceDecimal {
    var sum = PriceDecimal.ZERO
    for (element in this) sum += selector(element)
    return sum
}
